package io.hellsinger.navigation.route

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.view.animation.DecelerateInterpolator
import io.hellsinger.navigation.screen.HostNavPopupView
import io.hellsinger.navigation.screen.HostNavScreenView
import io.hellsinger.navigation.screen.ScreenController

class DefaultAndroidRouter(
    private val mapper: RouteMapper<ScreenController<*>?>,
    private val container: HostNavScreenView,
    private val popup: HostNavPopupView,
    private val onStackChanged: (index: Int) -> Unit,
) : AndroidRouter {
    private val stack = DefaultNavStack()

    override fun init(route: String) {
        init(screen = mapper.onMapRoute(route) ?: return)
    }

    fun canNavigateBack(): Boolean = stack.current > 0

    fun init(screen: ScreenController<*>) {
        stack.push(screen)
        screen.attach(this)
        screen.onPrepare()
        container.addView(screen.onCreateView(container.context))

        screen.onFocus()
    }

    override fun <A> navigate(
        route: String,
        args: A?,
    ) {
        navigate(
            screen = mapper.onMapRoute(route) as? ScreenController<A> ?: return,
            args = args,
        )
    }

    override fun <A> navigate(
        screen: ScreenController<A>,
        args: A?,
    ) {
        if (screen.usePopupMode) return navigatePopup(screen, args)

        var presentScreen = stack[stack.current]

        // Hide all popup before navigating screens
        while (presentScreen.usePopupMode) {
            hidePopup(stack.pop())
            presentScreen = stack[stack.current]
        }

        val presentView = presentScreen.onCreateView(container.context)
        val nextView = screen.onCreateView(container.context)

        stack.push(screen)
        screen.attach(this)
        args?.let(screen::setArgs)
        screen.onPrepare()

        container.addView(nextView)

        val animator = createAnimator()

        val distance = presentView.width

        animator.addUpdateListener { anim ->
            val factor = anim.animatedFraction

            presentView.translationX = -distance * factor
            nextView.translationX = distance - (distance * factor)
        }

        animator.addListener(
            object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator) {
                    container.removeView(presentView)
                    presentScreen.onHide()
                    presentScreen.detach()
                    screen.onFocus()
                }

                override fun onAnimationCancel(animation: Animator) {
                    nextView.translationX = 0F
                    container.removeView(presentView)
                    presentScreen.onHide()
                    presentScreen.detach()
                    screen.onFocus()
                }
            },
        )
        animator.start()
    }

    fun onActivityResult(
        requestCode: Int,
        resultCode: Int,
        data: Intent?,
    ): Boolean = stack[stack.current].onActivityResult(requestCode, resultCode, data)

    override fun popTo(route: String) {
        mapper.onMapRoute(route)?.let { controller -> popTo(controller::class.java) }
    }

    override fun <T : ScreenController<*>> popTo(controller: Class<T>) {
        val presentScreen = stack.pop()
        var previous = stack[stack.current]
        while (previous::class.java != controller) {
            container.removeView(previous.onCreateView(container.context))
            previous.onHide()
            previous.detach()
            previous.onDestroy()
            stack.pop()
            previous = stack[stack.current]
        }

        val presentView = presentScreen.onCreateView(container.context)
        val previousView = previous.onCreateView(container.context)

        previous.attach(this)
        previous.onPrepare()
        container.addView(previousView)

        val animator = createAnimator()

        val distance = presentView.width

        animator.addUpdateListener { anim ->
            val factor = anim.animatedFraction

            presentView.translationX = distance * factor
            previousView.translationX = -distance * (1F - factor)
        }

        animator.addListener(
            object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator) {
                    container.removeView(presentView)
                    presentScreen.onHide()
                    presentScreen.detach()
                    presentScreen.onDestroy()
                    previous.onFocus()
                }

                override fun onAnimationCancel(animation: Animator) {
                    previousView.translationX = 0F
                    container.removeView(presentView)
                    presentScreen.onHide()
                    presentScreen.detach()
                    previous.onFocus()
                }
            },
        )
        animator.start()
    }

    override fun navigateBack() {
        if (stack[stack.current].usePopupMode) return navigateBackPopup()
        val presentScreen = stack.pop()
        val screen = stack[stack.current]

        val presentView = presentScreen.onCreateView(container.context)
        val previousView = screen.onCreateView(container.context)

        val animator = createAnimator()

        val distance = presentView.width

        screen.attach(this)
        screen.onPrepare()
        container.addView(previousView)

        animator.addUpdateListener { anim ->
            val factor = anim.animatedFraction

            presentView.translationX = distance * factor
            previousView.translationX = -distance * (1F - factor)
        }

        animator.addListener(
            object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator) {
                    container.removeView(presentView)
                    presentScreen.onHide()
                    presentScreen.detach()
                    presentScreen.onDestroy()
                    screen.onFocus()
                }

                override fun onAnimationCancel(animation: Animator) {
                    previousView.translationX = 0F
                    container.removeView(presentView)
                    presentScreen.onHide()
                    presentScreen.detach()
                    screen.onFocus()
                }
            },
        )
        animator.start()
    }

    private fun <A> navigatePopup(
        screen: ScreenController<A>,
        args: A?,
    ) {
        val current = stack[stack.current]
        val popupView = screen.onCreateView(container.context)
        popup.scrim.setOnClickListener { navigateBackPopup() }

        // Popup to popup - proceed view change transition
        if (current.usePopupMode) TODO("Implement popup content view navigation")

        stack.push(screen)

        screen.attach(this)
        args?.let(screen::setArgs)
        screen.onPrepare()
        popup.addView(popupView)

        val animator = createAnimator()
        animator.addListener(
            object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator) {
                    current.onHide()
                    current.detach()
                    screen.onFocus()
                }

                override fun onAnimationCancel(animation: Animator) = onAnimationEnd(animation)
            },
        )
        animator.addUpdateListener { anim ->
            val factor = anim.animatedFraction
            popup.scrim.alpha = factor
            popupView.translationY = popupView.height * (1F - factor)
        }
        popup.create()

        val listener =
            object : View.OnLayoutChangeListener {
                override fun onLayoutChange(
                    v: View,
                    l: Int,
                    t: Int,
                    r: Int,
                    b: Int,
                    ol: Int,
                    ot: Int,
                    or: Int,
                    ob: Int,
                ) {
                    v.removeOnLayoutChangeListener(this)
                    animator.start()
                }
            }

        popup.addOnLayoutChangeListener(listener)
        popup.show(container)
    }

    private fun navigateBackPopup() {
        val presentScreen = stack.pop()
        val screen = stack[stack.current]

        val presentView = presentScreen.onCreateView(container.context)

        // Popup to popup - proceed view change transition
        if (screen.usePopupMode) TODO("Implement popup content view navigation")

        screen.attach(this)
        screen.onPrepare()

        val animator = createAnimator()
        animator.addListener(
            object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator) {
                    popup.removeView(presentView)
                    popup.hide()
                    presentScreen.onHide()
                    presentScreen.detach()
                    presentScreen.onDestroy()
                    screen.onFocus()
                }

                override fun onAnimationCancel(animation: Animator) {
                    popup.scrim.alpha = 0F
                    onAnimationEnd(animation)
                }
            },
        )
        animator.addUpdateListener { anim ->
            val factor = anim.animatedFraction

            popup.scrim.alpha = 1F - factor
            presentView.translationY = presentView.height * factor
        }
        animator.start()
    }

    private fun hidePopup(screen: ScreenController<*>) {
        val presentView = screen.onCreateView(container.context)
        val animator = createAnimator()
        animator.addListener(
            object : AnimatorListenerAdapter() {
                override fun onAnimationEnd(animation: Animator) {
                    popup.removeView(presentView)
                    popup.hide()
                    screen.onHide()
                    screen.detach()
                    screen.onDestroy()
                }

                override fun onAnimationCancel(animation: Animator) {
                    popup.scrim.alpha = 0F
                    onAnimationEnd(animation)
                }
            },
        )
        animator.addUpdateListener { anim ->
            val factor = anim.animatedFraction

            popup.scrim.alpha = 1F - factor
            presentView.translationY = presentView.height * factor
        }
        animator.start()
    }

    override fun restore(buffer: Bundle) {
    }

    override fun save(buffer: Bundle) {
    }

    fun onBackInvoked(): Boolean {
        if (stack.current >= 0) {
            if (stack[stack.current].onBackActivated()) {
                return true
            }
        }

        if (canNavigateBack()) {
            navigateBack()
            return true
        }
        return false
    }

    private fun createAnimator(): ValueAnimator =
        ValueAnimator.ofFloat(INITIAL_START_FACTOR, INITIAL_END_FACTOR).apply {
            duration = TRANSITION_DURATION
            interpolator = DecelerateInterpolator(DEFAULT_DECELERATE_FACTOR)
        }

    companion object {
        const val INITIAL_START_FACTOR = 0F
        const val INITIAL_END_FACTOR = 1F
        const val DEFAULT_DECELERATE_FACTOR = 1.78F
        const val TRANSITION_DURATION = 300L // 0.3 s
    }
}
